package security;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

import org.bouncycastle.util.encoders.Base64;

import util.KeyReader;

/**
 * Provides a channel with authentication functionality (server-side).
 * After authentication the communication is AES-encoded.
 * 
 * @author Lukas
 *
 */
public class ServerAuthenticationChannel extends ChannelDecorator {
	private Channel secureChannel = null;
	private Channel unsecureChannel;
	private Channel loginChannel;
	
	/**
	 * Constructor.
	 * @param channel The underlying channel that shall be used for unsecure transmission.
	 * @throws IOException
	 */
	public ServerAuthenticationChannel(Channel channel) throws IOException {
		unsecureChannel = channel;
		PrivateKey priKey = KeyReader.getInstance().readPrivateKey(true,"");
		loginChannel = new RSAChannel(channel, null, priKey);
		this.channel = channel;
	}
	
	@Override
	public String receive() throws IOException {
		String message;
		if (channel == unsecureChannel) {
			message = loginChannel.receive();
		} else {
			message = channel.receive();
		}
		if (message.equals("!unsecure")) {
			channel = unsecureChannel;
			message = channel.receive();
		}
		if (message.equals("!lastSecure")) {
			message = channel.receive();
			channel = unsecureChannel;
		}
		return message;
	}
	
	/**
	 * Shall be called after receiving !login, if the login is accepted.
	 * @param message The !login-message from the client.
	 * @throws IOException
	 */
	public void loginProcess(String message) throws IOException {
		try {
			String[] command = message.split("\\s+");
			String user = command[1];
			if (!command[0].equals("!login")) {
				throw new SecurityException("Not a login-message!");
			}
			
			String response = "!ok " + command[3];
			
			SecureRandom rand = new SecureRandom();
			final byte[] serverChallenge = new byte[32];
			rand.nextBytes(serverChallenge);
			response += ' ' + new String(Base64.encode(serverChallenge));
			//response += ' ' + "Test";
			
			KeyGenerator generator;
			try {
				generator = KeyGenerator.getInstance("AES");
			} catch (NoSuchAlgorithmException e) {
				throw new SecurityException();
			}
			generator.init(256);
			SecretKey secKey = generator.generateKey();
			final byte[] ivBytes = new byte[16];
			rand.nextBytes(ivBytes);
			AlgorithmParameterSpec ivPar = new IvParameterSpec(ivBytes);
			
			response += ' ' + new String(Base64.encode(secKey.getEncoded()));
			response += ' ' + new String(Base64.encode(ivBytes));
			
			KeyReader kr = KeyReader.getInstance();
			//kr.setUserName(user);
			PublicKey pubKey = kr.readPublicKey(user);
			PrivateKey priKey = kr.readPrivateKey(true, "");
			
			//Base64Channel b64Channel = new Base64Channel(channel);
			RSAChannel rsaChannel = new RSAChannel(unsecureChannel, pubKey, priKey);
			rsaChannel.send(response);
			
			secureChannel = new AESChannel(unsecureChannel, secKey, ivPar);
			message = secureChannel.receive();
			
			byte[] challengeResponse = Base64.decode(message);
			if (challengeResponse.length != serverChallenge.length) {
				throw new SecurityException();
			}
			for (int i = 0; i < challengeResponse.length; i++) {
				if (challengeResponse[i] != serverChallenge[i]) {
					throw new SecurityException();
				}
			}
			channel = secureChannel;
		} catch (SecurityException e) {
			channel = unsecureChannel;
			channel.send("!unsecure");
			channel.send("Login failed! Handshake-Problem detected.");
			throw e;
		}
	}
	
	/**
	 * Send the message and switch to unsecure channel after that.
	 * @param message The last secure message.
	 * @throws IOException
	 */
	public void stopSecurity(String message) throws IOException {
		channel.send("!lastSecure");
		channel.send(message);
		channel = unsecureChannel;
	}
	
	/**
	 * Switch to unsecure channel.
	 * @throws IOException
	 */
	public void stopSecurity() throws IOException {
		channel = unsecureChannel;
		channel.send("!unsecure");
	}
}
